|
1
|
|
|
/*! |
|
2
|
|
|
* @package ElkArte Forum |
|
3
|
|
|
* @copyright ElkArte Forum contributors |
|
4
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
|
5
|
|
|
* |
|
6
|
|
|
* @version 2.0 dev |
|
7
|
|
|
* |
|
8
|
|
|
* Original code from Aziz, redone and refactored for ElkArte |
|
9
|
|
|
*/ |
|
10
|
|
|
|
|
11
|
|
|
/** global: elk_session_id, elk_session_var, elk_scripturl */ |
|
12
|
|
|
|
|
13
|
|
|
/** |
|
14
|
|
|
* This javascript searches the message for video links and replaces them |
|
15
|
|
|
* with a clickable preview thumbnail of the video. Once the image is clicked |
|
16
|
|
|
* the video is embedded in to the page to play. |
|
17
|
|
|
* |
|
18
|
|
|
* Currently, works with YouTube, Vimeo, TikTok, Twitter, Facebook, Instagram and DailyMotion |
|
19
|
|
|
* |
|
20
|
|
|
*/ |
|
21
|
|
|
(function($) { |
|
22
|
|
|
'use strict'; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* @param {object} oInstanceSettings holds the text strings to use in the html created |
|
26
|
|
|
* @param {int} msgid optional to only search for links in a specific id |
|
27
|
|
|
*/ |
|
28
|
|
|
$.fn.linkifyvideo = function(oInstanceSettings, msgid) { |
|
29
|
|
|
let oDefaultsSettings = { |
|
30
|
|
|
embed_limit: 25, |
|
31
|
|
|
preview_image: '', |
|
32
|
|
|
ctp_video: '', |
|
33
|
|
|
hide_video: '', |
|
34
|
|
|
youtube: '', |
|
35
|
|
|
vimeo: '', |
|
36
|
|
|
dailymotion: '', |
|
37
|
|
|
tiktok: '', |
|
38
|
|
|
twitter: '', |
|
39
|
|
|
facebook: '', |
|
40
|
|
|
instagram: '' |
|
41
|
|
|
}; |
|
42
|
|
|
|
|
43
|
|
|
// Account for user options |
|
44
|
|
|
let oSettings = $.extend({}, oDefaultsSettings, oInstanceSettings || {}); |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* Replaces the image with the created embed code to show the video |
|
48
|
|
|
* Called from click event attached to the image |
|
49
|
|
|
* |
|
50
|
|
|
* @param {string} tag anchor tag we are replacing with the embed tag |
|
51
|
|
|
* @param {string} eURL the load or place source link |
|
52
|
|
|
* @param {boolean} bAspect if to use a tall vs wide |
|
53
|
|
|
*/ |
|
54
|
|
|
function showEmbed (tag, eURL, bAspect) |
|
55
|
|
|
{ |
|
56
|
|
|
if (bAspect) |
|
57
|
|
|
{ |
|
58
|
|
|
$(tag).html(embed_html.replace('{src}', eURL)); |
|
|
|
|
|
|
59
|
|
|
} |
|
60
|
|
|
else |
|
61
|
|
|
{ |
|
62
|
|
|
$(tag).html(embed_html_916.replace('{src}', eURL)); |
|
|
|
|
|
|
63
|
|
|
} |
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Shows the video image and sets up the link |
|
68
|
|
|
* Sets click event to load video sites embed code |
|
69
|
|
|
* |
|
70
|
|
|
* @param {object} a videoID link |
|
71
|
|
|
* @param {string} src source of image |
|
72
|
|
|
* @param {string} eURLa play link |
|
73
|
|
|
* @param {boolean} bAspect false to use a 9/16 iframe vs 16x9 |
|
74
|
|
|
*/ |
|
75
|
|
|
function getIMG (a, src, eURLa, bAspect) |
|
76
|
|
|
{ |
|
77
|
|
|
return $('' + |
|
78
|
|
|
'<div class="elk_video">' + |
|
79
|
|
|
' <a href="' + a.href + '">' + |
|
80
|
|
|
' <img class="elk_video_preview" alt="' + oSettings.preview_image + '" ' + 'title="' + oSettings.ctp_video + '" src="' + src + '"/>' + |
|
81
|
|
|
' </a>' + |
|
82
|
|
|
'</div>') |
|
83
|
|
|
.on('click', function(e) { |
|
84
|
|
|
e.preventDefault(); |
|
85
|
|
|
let tag = this; |
|
86
|
|
|
showEmbed(tag, eURLa, bAspect); |
|
87
|
|
|
} |
|
88
|
|
|
); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Returns a linked preview image. Click on the image to load the player. |
|
93
|
|
|
* |
|
94
|
|
|
* @param {string} a link tag of the video |
|
95
|
|
|
* @param {string} src link of the preview image |
|
96
|
|
|
* @param {string} eURLa single click event play video |
|
97
|
|
|
* @param {boolean} bAspect use a wide vs tall ratio |
|
98
|
|
|
*/ |
|
99
|
|
|
function embedIMG (a, src, eURLa, bAspect = true) |
|
100
|
|
|
{ |
|
101
|
|
|
return getIMG(a, src, eURLa, bAspect); |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
/** |
|
105
|
|
|
* Creates and inserts a document fragment. Doing this vs inner/outer HTML ensures that any script |
|
106
|
|
|
* tags in the embed code will execute. |
|
107
|
|
|
* |
|
108
|
|
|
* @param {Element} a the link we are working with |
|
109
|
|
|
* @param {object} data the data from the ajax call |
|
110
|
|
|
*/ |
|
111
|
|
|
function createFragment (a, data) |
|
112
|
|
|
{ |
|
113
|
|
|
// Since data.html may contain a script tag that needs to run, we have to add it like this |
|
114
|
|
|
let parent = a.parentNode, |
|
115
|
|
|
frag = document.createRange().createContextualFragment('<div class="elk_video">' + data.html + '</div>'); |
|
116
|
|
|
|
|
117
|
|
|
parent.parentNode.appendChild(frag); |
|
118
|
|
|
parent.nextSibling.outerHTML = ''; |
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* Converts a given time value from an array to seconds using a multiplier. |
|
123
|
|
|
* |
|
124
|
|
|
* @param {Array} timeArray - The array containing time values. |
|
125
|
|
|
* @param {number} timeIndex - The index of the time value to convert. |
|
126
|
|
|
* @param {number} multiplier - The multiplier to apply to the converted time value. |
|
127
|
|
|
* @return {number} - The converted time value in seconds, or 0 if the time array is undefined or the timeIndex is out of range. |
|
128
|
|
|
*/ |
|
129
|
|
|
function convertToSeconds (timeArray, timeIndex, multiplier) |
|
130
|
|
|
{ |
|
131
|
|
|
if (typeof timeArray[timeIndex] !== 'undefined') |
|
132
|
|
|
{ |
|
133
|
|
|
return parseInt(timeArray[timeIndex]) * multiplier; |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
return 0; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
// The embed code |
|
140
|
|
|
let domain_regex = /^[^:]*:\/\/(?:www\.)?([^\/]+)(\/.*)$/, |
|
141
|
|
|
embedded_count = 0, |
|
142
|
|
|
provider_class = '', |
|
143
|
|
|
embed_html = '<iframe width="640" height="360" src="{src}" data-autoplay="true" allow="fullscreen" loading="lazy" type="text/html"></iframe>', |
|
|
|
|
|
|
144
|
|
|
embed_html_916 = '<iframe width="480" height="800" src="{src}" allow="fullscreen" loading="lazy" type="text/html"></iframe>', |
|
|
|
|
|
|
145
|
|
|
handlers = {}, |
|
146
|
|
|
imgHandlers = {}, |
|
147
|
|
|
logos = { |
|
148
|
|
|
tiktok: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 48 48\'%3E%3Cpath fill=\'%23fff\' fill-opacity=\'.01\' d=\'M0 0h48v48H0z\'/%3E%3Cpath fill=\'%232F88FF\' stroke=\'%23000\' stroke-linejoin=\'round\' stroke-width=\'3.833\' d=\'M21.358 19.14c-5.888-.284-9.982 1.815-12.28 6.299-3.446 6.724-.597 17.728 10.901 17.728 11.499 0 11.831-11.111 11.831-12.276V17.876c2.46 1.557 4.533 2.495 6.221 2.813 1.688.317 2.76.458 3.219.422v-6.476c-1.561-.188-2.911-.547-4.05-1.076-1.709-.794-5.096-2.997-5.096-6.226.002.016.002-.817 0-2.499h-7.118c-.021 15.816-.021 24.502 0 26.058.032 2.334-1.779 5.6-5.45 5.6-3.672 0-5.482-3.263-5.482-5.367 0-1.288.442-3.155 2.271-4.538 1.085-.82 2.59-1.147 5.033-1.147V19.14Z\'/%3E%3C/svg%3E', |
|
149
|
|
|
vimeo: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 455.731 455.731\'%3E%3Cpath fill=\'%231ab7ea\' d=\'M0 0h455.731v455.731H0z\'/%3E%3Cpath fill=\'%23fff\' d=\'m49.642 157.084 17.626 22.474s22.033-17.186 29.965-17.186c4.927 0 15.423 5.729 22.033 25.558 6.61 19.83 34.441 122.62 36.134 127.351 7.607 21.26 17.626 60.811 48.473 66.54s70.065-25.558 91.657-48.473c21.592-22.914 106.64-120.741 110.165-179.349 3.26-54.191-14.517-66.765-22.474-71.828-14.542-9.254-38.778-12.338-61.692-4.407s-57.726 33.931-66.98 80.2c0 0 31.287-11.457 42.744-.441s8.373 35.253-1.322 53.32-37.015 59.93-47.151 61.252c-10.135 1.322-18.067-18.508-19.389-23.796-1.322-5.288-18.067-77.997-24.236-120.3s-33.049-49.354-45.829-49.354c-12.779.001-34.812 9.696-109.724 78.439z\'/%3E%3C/svg%3E', |
|
150
|
|
|
dailymotion: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 512 512\'%3E%3Cpath fill=\'%230066DC\' fill-rule=\'evenodd\' d=\'M0 512h512V0H0v512Zm441.5-68.635h-76.314v-29.928c-23.443 22.945-47.385 31.424-79.308 31.424-32.421 0-60.354-10.474-83.797-31.424-30.926-27.433-46.887-63.346-46.887-105.245 0-38.407 14.965-72.823 42.896-99.758 24.94-24.44 55.367-36.91 89.284-36.91 32.422 0 57.361 10.973 75.318 33.917V88.724L441.5 72.395v370.97Zm-141.157-202.01c-37.41 0-66.339 30.426-66.339 66.338 0 37.41 28.93 65.841 69.332 65.841 33.918 0 62.349-27.932 62.349-64.843 0-38.406-28.431-67.336-65.342-67.336Z\'/%3E%3C/svg%3E', |
|
151
|
|
|
twitter: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'126 2 589 589\'%3E%3Ccircle cx=\'420.944\' cy=\'296.781\' r=\'294.5\' fill=\'%232daae1\'/%3E%3Cpath fill=\'%23fff\' d=\'M609.773 179.634c-13.891 6.164-28.811 10.331-44.498 12.204 16.01-9.587 28.275-24.779 34.066-42.86a154.78 154.78 0 0 1-49.209 18.801c-14.125-15.056-34.267-24.456-56.551-24.456-42.773 0-77.462 34.675-77.462 77.473 0 6.064.683 11.98 1.996 17.66-64.389-3.236-121.474-34.079-159.684-80.945-6.672 11.446-10.491 24.754-10.491 38.953 0 26.875 13.679 50.587 34.464 64.477a77.122 77.122 0 0 1-35.097-9.686v.979c0 37.54 26.701 68.842 62.145 75.961-6.511 1.784-13.344 2.716-20.413 2.716-4.998 0-9.847-.473-14.584-1.364 9.859 30.769 38.471 53.166 72.363 53.799-26.515 20.785-59.925 33.175-96.212 33.175-6.25 0-12.427-.373-18.491-1.104 34.291 21.988 75.006 34.824 118.759 34.824 142.496 0 220.428-118.052 220.428-220.428 0-3.361-.074-6.697-.236-10.021a157.855 157.855 0 0 0 38.707-40.158z\'/%3E%3C/svg%3E', |
|
152
|
|
|
facebook: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 128 128\'%3E%3Cpath fill=\'%233B5998\' d=\'M126 118a8 8 0 0 1-8 8H10a8 8 0 0 1-8-8V10a8 8 0 0 1 8-8h108a8 8 0 0 1 8 8v108z\'/%3E%3Cpath fill=\'%236D84B4\' d=\'M5.667 98.98h116.666v18.039H5.667z\'/%3E%3Cpath fill=\'%23FFF\' d=\'M93.376 117.012H72.203V65.767H61.625v-17.66h10.578V37.504c0-14.407 5.973-22.974 22.943-22.974h14.128v17.662h-8.831c-6.606 0-7.043 2.468-7.043 7.074l-.024 8.839h15.998l-1.872 17.66H93.376v51.247z\'/%3E%3C/svg%3E', |
|
153
|
|
|
instagram: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 256 256\'%3E%3Cpath fill=\'%230A0A08\' d=\'M128 23.064c34.177 0 38.225.13 51.722.745 12.48.57 19.258 2.655 23.769 4.408 5.974 2.322 10.238 5.096 14.717 9.575 4.48 4.479 7.253 8.743 9.575 14.717 1.753 4.511 3.838 11.289 4.408 23.768.615 13.498.745 17.546.745 51.723 0 34.178-.13 38.226-.745 51.723-.57 12.48-2.655 19.257-4.408 23.768-2.322 5.974-5.096 10.239-9.575 14.718-4.479 4.479-8.743 7.253-14.717 9.574-4.511 1.753-11.289 3.839-23.769 4.408-13.495.616-17.543.746-51.722.746-34.18 0-38.228-.13-51.723-.746-12.48-.57-19.257-2.655-23.768-4.408-5.974-2.321-10.239-5.095-14.718-9.574-4.479-4.48-7.253-8.744-9.574-14.718-1.753-4.51-3.839-11.288-4.408-23.768-.616-13.497-.746-17.545-.746-51.723 0-34.177.13-38.225.746-51.722.57-12.48 2.655-19.258 4.408-23.769 2.321-5.974 5.095-10.238 9.574-14.717 4.48-4.48 8.744-7.253 14.718-9.575 4.51-1.753 11.288-3.838 23.768-4.408 13.497-.615 17.545-.745 51.723-.745M128 0C93.237 0 88.878.147 75.226.77c-13.625.622-22.93 2.786-31.071 5.95-8.418 3.271-15.556 7.648-22.672 14.764C14.367 28.6 9.991 35.738 6.72 44.155 3.555 52.297 1.392 61.602.77 75.226.147 88.878 0 93.237 0 128c0 34.763.147 39.122.77 52.774.622 13.625 2.785 22.93 5.95 31.071 3.27 8.417 7.647 15.556 14.763 22.672 7.116 7.116 14.254 11.492 22.672 14.763 8.142 3.165 17.446 5.328 31.07 5.95 13.653.623 18.012.77 52.775.77s39.122-.147 52.774-.77c13.624-.622 22.929-2.785 31.07-5.95 8.418-3.27 15.556-7.647 22.672-14.763 7.116-7.116 11.493-14.254 14.764-22.672 3.164-8.142 5.328-17.446 5.95-31.07.623-13.653.77-18.012.77-52.775s-.147-39.122-.77-52.774c-.622-13.624-2.786-22.929-5.95-31.07-3.271-8.418-7.648-15.556-14.764-22.672C227.4 14.368 220.262 9.99 211.845 6.72c-8.142-3.164-17.447-5.328-31.071-5.95C167.122.147 162.763 0 128 0Zm0 62.27C91.698 62.27 62.27 91.7 62.27 128c0 36.302 29.428 65.73 65.73 65.73 36.301 0 65.73-29.428 65.73-65.73 0-36.301-29.429-65.73-65.73-65.73Zm0 108.397c-23.564 0-42.667-19.103-42.667-42.667S104.436 85.333 128 85.333s42.667 19.103 42.667 42.667-19.103 42.667-42.667 42.667Zm83.686-110.994c0 8.484-6.876 15.36-15.36 15.36-8.483 0-15.36-6.876-15.36-15.36 0-8.483 6.877-15.36 15.36-15.36 8.484 0 15.36 6.877 15.36 15.36Z\'/%3E%3C/svg%3E', |
|
154
|
|
|
youtube: 'data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'-100 -100 661 661\'%3E%3Cpath d=\'M365.257 67.393H95.744C42.866 67.393 0 110.259 0 163.137v134.728c0 52.878 42.866 95.744 95.744 95.744h269.513c52.878 0 95.744-42.866 95.744-95.744V163.137c0-52.878-42.866-95.744-95.744-95.744zm-64.751 169.663-126.06 60.123c-3.359 1.602-7.239-.847-7.239-4.568V168.607c0-3.774 3.982-6.22 7.348-4.514l126.06 63.881c3.748 1.899 3.683 7.274-.109 9.082z\' style=\'fill:%23f61c0d\'/%3E%3C/svg%3E', |
|
155
|
|
|
}; |
|
156
|
|
|
|
|
157
|
|
|
// Get a Youtube video thumbnail |
|
158
|
|
|
imgHandlers.getYoutubeIMG = function(eURL, callback) { |
|
159
|
|
|
fetchDocument(eURL, ytResponse, 'json'); |
|
160
|
|
|
|
|
161
|
|
|
function ytResponse (data) |
|
162
|
|
|
{ |
|
163
|
|
|
if (typeof data.thumbnail_url === 'undefined') |
|
164
|
|
|
{ |
|
165
|
|
|
callback(logos.youtube); |
|
166
|
|
|
} |
|
167
|
|
|
else |
|
168
|
|
|
{ |
|
169
|
|
|
callback(data.thumbnail_url); |
|
170
|
|
|
} |
|
171
|
|
|
} |
|
172
|
|
|
}; |
|
173
|
|
|
|
|
174
|
|
|
// Get a twitter embed html |
|
175
|
|
|
imgHandlers.getTwitterEmbed = function(eURL, callback) { |
|
176
|
|
|
fetchDocument(eURL, twResponse, 'json'); |
|
177
|
|
|
|
|
178
|
|
|
function twResponse (data) |
|
179
|
|
|
{ |
|
180
|
|
|
if (typeof data.html === 'undefined') |
|
181
|
|
|
{ |
|
182
|
|
|
data.html = ''; |
|
183
|
|
|
} |
|
184
|
|
|
|
|
185
|
|
|
callback(data); |
|
186
|
|
|
} |
|
187
|
|
|
}; |
|
188
|
|
|
|
|
189
|
|
|
// Get a TikTok video thumbnail and embed data |
|
190
|
|
|
imgHandlers.getTikTokEmbed = function(eURL, callback) { |
|
191
|
|
|
fetchDocument(eURL, ttResponse, 'json', false); |
|
192
|
|
|
|
|
193
|
|
|
function ttResponse (data) |
|
194
|
|
|
{ |
|
195
|
|
|
if (typeof data.html === 'undefined') |
|
196
|
|
|
{ |
|
197
|
|
|
data.thumbnail_url = logos.tiktok; |
|
198
|
|
|
data.html = ''; |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
callback(data); |
|
202
|
|
|
} |
|
203
|
|
|
}; |
|
204
|
|
|
|
|
205
|
|
|
// Get a dailymotion video thumbnail |
|
206
|
|
|
imgHandlers.getDailymotionIMG = function(eURL, callback) { |
|
207
|
|
|
fetchDocument(eURL, dailyResponse, 'json', false); |
|
208
|
|
|
|
|
209
|
|
|
function dailyResponse (data) |
|
210
|
|
|
{ |
|
211
|
|
|
if (typeof data.thumbnail_480_url === 'undefined') |
|
212
|
|
|
{ |
|
213
|
|
|
callback(logos.dailymotion); |
|
214
|
|
|
} |
|
215
|
|
|
else |
|
216
|
|
|
{ |
|
217
|
|
|
callback(data.thumbnail_480_url); |
|
218
|
|
|
} |
|
219
|
|
|
} |
|
220
|
|
|
}; |
|
221
|
|
|
|
|
222
|
|
|
// Get a Vimeo video thumbnail |
|
223
|
|
|
imgHandlers.getVimeoIMG = function(eURL, callback) { |
|
224
|
|
|
fetchDocument(eURL, vimeoResponse, 'json', false); |
|
225
|
|
|
|
|
226
|
|
|
function vimeoResponse (data) |
|
227
|
|
|
{ |
|
228
|
|
|
if (typeof data[0].thumbnail_large === 'undefined') |
|
229
|
|
|
{ |
|
230
|
|
|
callback(logos.vimeo); |
|
231
|
|
|
} |
|
232
|
|
|
else |
|
233
|
|
|
{ |
|
234
|
|
|
callback(data[0].thumbnail_large); |
|
235
|
|
|
} |
|
236
|
|
|
} |
|
237
|
|
|
}; |
|
238
|
|
|
|
|
239
|
|
|
// Youtube and variants |
|
240
|
|
|
handlers['youtube.com'] = function(path, a) { |
|
241
|
|
|
let videoID = path.match(/\bv[=/]([^&#?$]+)/i) || path.match(/#p\/(?:a\/)?[uf]\/\d+\/([^?$]+)/i) || path.match(/(?:\/)([\w-]{11})/i); |
|
242
|
|
|
|
|
243
|
|
|
if (!videoID || !(videoID = videoID[1])) |
|
244
|
|
|
{ |
|
245
|
|
|
return; |
|
|
|
|
|
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
// There are two types of YouTube timestamped links |
|
249
|
|
|
// http://youtu.be/lLOE3fBZcUU?t=1m37s when you click share underneath the video |
|
250
|
|
|
// http://youtu.be/lLOE3fBZcUU?t=97 when you right click on a video and choose "Copy video URL at current time" |
|
251
|
|
|
// For embedding, you need to use "?start=97" instead, so we have to convert t=1m37s to seconds while also supporting t=97 |
|
252
|
|
|
let startAt = path.match(/t=(?:([1-9]{1,2})h)?(?:([1-9]{1,2})m)?(?:([1-9]+)s?)/), |
|
253
|
|
|
startAtPar = ''; |
|
254
|
|
|
|
|
255
|
|
|
if (startAt) |
|
256
|
|
|
{ |
|
257
|
|
|
let startAtSeconds = 0; |
|
258
|
|
|
|
|
259
|
|
|
startAtSeconds += convertToSeconds(startAt, 1, 3600); // Hours |
|
260
|
|
|
startAtSeconds += convertToSeconds(startAt, 2, 60); // Minutes |
|
261
|
|
|
startAtSeconds += convertToSeconds(startAt, 3, 1); // Seconds |
|
262
|
|
|
|
|
263
|
|
|
startAtPar = '&start=' + startAtSeconds.toString(); |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
let embedURL = '//www.youtube-nocookie.com/embed/' + videoID + '?rel=0' + startAtPar, |
|
267
|
|
|
imgURL = '//www.youtube.com/oembed?url=https://www.youtube.com/watch?v=' + videoID + '&format=json', |
|
268
|
|
|
tag = embedIMG(a, '//i.ytimg.com/vi/' + videoID + '/sddefault.jpg', embedURL + '&autoplay=1'); |
|
269
|
|
|
|
|
270
|
|
|
// Get the preview image / embed tag |
|
271
|
|
|
imgHandlers.getYoutubeIMG(imgURL, function(img) { |
|
272
|
|
|
$(a).parent().next().find('img').attr('src', img); |
|
273
|
|
|
}); |
|
274
|
|
|
|
|
275
|
|
|
return [oSettings.youtube, tag]; |
|
276
|
|
|
}; |
|
277
|
|
|
handlers['m.youtube.com'] = handlers['youtube.com']; |
|
278
|
|
|
handlers['youtu.be'] = handlers['youtube.com']; |
|
279
|
|
|
|
|
280
|
|
|
// Vimeo |
|
281
|
|
|
handlers['vimeo.com'] = function(path, a) { |
|
282
|
|
|
let videoID = path.match(/^\/(\d+)/i); |
|
283
|
|
|
|
|
284
|
|
|
if (!videoID || !(videoID = videoID[1])) |
|
285
|
|
|
{ |
|
286
|
|
|
return; |
|
|
|
|
|
|
287
|
|
|
} |
|
288
|
|
|
|
|
289
|
|
|
let embedURL = '//player.vimeo.com/video/' + videoID, |
|
290
|
|
|
imgURL = '//vimeo.com/api/v2/video/' + videoID + '.json', |
|
291
|
|
|
tag; |
|
292
|
|
|
|
|
293
|
|
|
tag = embedIMG(a, logos.vimeo, embedURL + '?autoplay=1'); |
|
294
|
|
|
|
|
295
|
|
|
// Get the preview image / embed tag |
|
296
|
|
|
imgHandlers.getVimeoIMG(imgURL, function(img) { |
|
297
|
|
|
$(a).parent().next().find('img').attr('src', img); |
|
298
|
|
|
}); |
|
299
|
|
|
|
|
300
|
|
|
return [oSettings.vimeo, tag]; |
|
301
|
|
|
}; |
|
302
|
|
|
|
|
303
|
|
|
// Dailymotion |
|
304
|
|
|
handlers['dailymotion.com'] = function(path, a) { |
|
305
|
|
|
let videoID = path.match(/^\/video\/([a-z0-9]{1,18})/i); |
|
306
|
|
|
|
|
307
|
|
|
if (!videoID || videoID[1] === '') |
|
308
|
|
|
{ |
|
309
|
|
|
return; |
|
|
|
|
|
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
let embedURL = '//dailymotion.com/embed/video/' + videoID[1], |
|
313
|
|
|
imgURL = '//api.dailymotion.com/video/' + videoID[1] + '?fields=thumbnail_480_url', |
|
314
|
|
|
tag; |
|
315
|
|
|
|
|
316
|
|
|
tag = embedIMG(a, logos.dailymotion, embedURL + '?related=0&autoplay=1'); |
|
317
|
|
|
|
|
318
|
|
|
// Get the preview image or embed tag |
|
319
|
|
|
imgHandlers.getDailymotionIMG(imgURL, function(img) { |
|
320
|
|
|
$(a).parent().next().find('img').attr('src', img); |
|
321
|
|
|
}); |
|
322
|
|
|
|
|
323
|
|
|
return [oSettings.dailymotion, tag]; |
|
324
|
|
|
}; |
|
325
|
|
|
|
|
326
|
|
|
// TikTok |
|
327
|
|
|
handlers['tiktok.com'] = function(path, a) { |
|
328
|
|
|
let videoID = path.match(/^\/@([0-9A-Za-z_\-.]*)\/video\/([0-9]*)/i); |
|
329
|
|
|
|
|
330
|
|
|
if (!videoID) |
|
331
|
|
|
{ |
|
332
|
|
|
return; |
|
|
|
|
|
|
333
|
|
|
} |
|
334
|
|
|
|
|
335
|
|
|
let embedURL = '//www.tiktok.com/oembed?url=https://www.tiktok.com/@' + videoID[1] + '/video/' + videoID[2], |
|
336
|
|
|
tag; |
|
337
|
|
|
|
|
338
|
|
|
imgHandlers.getTikTokEmbed(embedURL, function(data) { |
|
339
|
|
|
$(a).parent().next().find('img').attr('src', data.thumbnail_url); |
|
340
|
|
|
a.embedURL = data.html; |
|
341
|
|
|
}); |
|
342
|
|
|
|
|
343
|
|
|
tag = embedIMG(a, logos.tiktok, embedURL); |
|
344
|
|
|
|
|
345
|
|
|
// Change the default click event to one that replaces the markup |
|
346
|
|
|
tag.off('click', '**', false); |
|
347
|
|
|
tag.on('click', function(e) { |
|
348
|
|
|
e.preventDefault(); |
|
349
|
|
|
let load = $(a).parent(); |
|
350
|
|
|
load.parent().addClass('portrait'); |
|
351
|
|
|
|
|
352
|
|
|
load.next().replaceWith('<div class="elk_video">' + a.embedURL + '</div>'); |
|
353
|
|
|
}); |
|
354
|
|
|
|
|
355
|
|
|
return [oSettings.tiktok, tag]; |
|
356
|
|
|
}; |
|
357
|
|
|
|
|
358
|
|
|
// Twitter |
|
359
|
|
|
handlers['twitter.com'] = function(path, a) { |
|
360
|
|
|
let videoID = path.match(/\/status\/([0-9]{16,20})/); |
|
361
|
|
|
|
|
362
|
|
|
if (!videoID || videoID[1] === '') |
|
363
|
|
|
{ |
|
364
|
|
|
return; |
|
|
|
|
|
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
let embedURL = elk_prepareScriptUrl(elk_scripturl) + 'action=xmlhttp;sa=videoembed;api=json;site=twitter;videoid=' + videoID[1] + ';' + elk_session_var + '=' + elk_session_id, |
|
368
|
|
|
tag; |
|
369
|
|
|
|
|
370
|
|
|
tag = embedIMG(a, logos.twitter, embedURL); |
|
371
|
|
|
|
|
372
|
|
|
// Twitter has its own embed codes we need to load, no preview here, replace click event as well |
|
373
|
|
|
tag.off('click', '**', false); |
|
374
|
|
|
a.embedURL = embedURL; |
|
375
|
|
|
a.setAttribute('data-video_embed', 'getTwitterEmbed'); |
|
376
|
|
|
|
|
377
|
|
|
return [oSettings.twitter, tag, 'portrait']; |
|
378
|
|
|
}; |
|
379
|
|
|
|
|
380
|
|
|
// Facebook |
|
381
|
|
|
handlers['facebook.com'] = function(path, a) { |
|
382
|
|
|
let videoID = path.match(/([\d\w._-]+)?(?:\/videos\/|\/video.php\?v=)(\d+)/i); |
|
383
|
|
|
|
|
384
|
|
|
if (!videoID || videoID[1] === '' || videoID[2] === '') |
|
385
|
|
|
{ |
|
386
|
|
|
return; |
|
|
|
|
|
|
387
|
|
|
} |
|
388
|
|
|
|
|
389
|
|
|
let embedURL = '//www.facebook.com/plugins/video.php?href=https://www.facebook.com/' + videoID[1] + '/videos/' + videoID[2], |
|
390
|
|
|
tag; |
|
391
|
|
|
|
|
392
|
|
|
tag = embedIMG(a, logos.facebook, embedURL + '?related=0&autoplay=1'); |
|
393
|
|
|
|
|
394
|
|
|
return [oSettings.facebook, tag]; |
|
395
|
|
|
}; |
|
396
|
|
|
|
|
397
|
|
|
// Instagram |
|
398
|
|
|
handlers['instagram.com'] = function(path, a) { |
|
399
|
|
|
let videoID = path.match(/\/(?:tv|p)\/([a-z0-9]{10,18})(?:\/\?|\/)?/i); |
|
400
|
|
|
|
|
401
|
|
|
if (!videoID || videoID[1] === '') |
|
402
|
|
|
{ |
|
403
|
|
|
return; |
|
|
|
|
|
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
let embedURL = '//www.instagram.com/p/' + videoID[1] + '/embed', |
|
407
|
|
|
tag; |
|
408
|
|
|
|
|
409
|
|
|
tag = embedIMG(a, logos.instagram, embedURL + '?related=0&autoplay=1', false); |
|
410
|
|
|
|
|
411
|
|
|
return [oSettings.instagram, tag, 'portrait']; |
|
412
|
|
|
}; |
|
413
|
|
|
|
|
414
|
|
|
// --------------------------------------------------------------------------- |
|
415
|
|
|
// Get the bbc_link links in the id="msg_1234 divs. |
|
416
|
|
|
let links; |
|
417
|
|
|
|
|
418
|
|
|
if (typeof msgid !== 'undefined') |
|
419
|
|
|
{ |
|
420
|
|
|
links = document.querySelectorAll('#' + msgid + ' a.bbc_link'); |
|
421
|
|
|
} |
|
422
|
|
|
else |
|
423
|
|
|
{ |
|
424
|
|
|
links = document.querySelectorAll('[id^=msg_] a.bbc_link'); |
|
425
|
|
|
} |
|
426
|
|
|
|
|
427
|
|
|
// Create the show/hide button |
|
428
|
|
|
let showhideBtn = $('' + |
|
429
|
|
|
'<a class="floatright" title="' + oSettings.hide_video + '">' + |
|
430
|
|
|
' <i class="icon icon-small i-caret-up" alt=">"></i>' + |
|
431
|
|
|
'</a>') |
|
432
|
|
|
.on('click', function() { |
|
433
|
|
|
let $img = $(this).find('i'), // The open / close icon |
|
434
|
|
|
$vid = $(this).parent().next(); // The immediate elk_video div |
|
435
|
|
|
|
|
436
|
|
|
// Toggle slide the video and change the icon |
|
437
|
|
|
$img.attr('class', 'icon icon-small ' + ($vid.is(':hidden') !== true ? 'i-caret-down' : 'i-caret-up')); |
|
438
|
|
|
$vid.slideToggle(); |
|
439
|
|
|
}); |
|
440
|
|
|
|
|
441
|
|
|
// Loop though each link |
|
442
|
|
|
links.forEach((link) => { |
|
443
|
|
|
let tag = link, |
|
444
|
|
|
text = tag.innerText || tag.textContent || ''; |
|
445
|
|
|
|
|
446
|
|
|
// Ignore in sentences |
|
447
|
|
|
if (tag.previousSibling && tag.previousSibling.nodeName === '#text' && tag.previousSibling.nodeValue !== ' ') |
|
448
|
|
|
{ |
|
449
|
|
|
return; |
|
450
|
|
|
} |
|
451
|
|
|
|
|
452
|
|
|
// Ignore in quotes and signatures |
|
453
|
|
|
if ('bbc_quote;signature'.indexOf(tag.parentNode.className) !== -1) |
|
454
|
|
|
{ |
|
455
|
|
|
return; |
|
456
|
|
|
} |
|
457
|
|
|
|
|
458
|
|
|
// No href or inner text not equal to href attr then we move along |
|
459
|
|
|
if (tag.href === '' || tag.href.indexOf(text) !== 0) |
|
460
|
|
|
{ |
|
461
|
|
|
return; |
|
462
|
|
|
} |
|
463
|
|
|
|
|
464
|
|
|
// Get domain and validate we know how to handle it |
|
465
|
|
|
let m = tag.href.match(domain_regex), |
|
466
|
|
|
handler = null, |
|
|
|
|
|
|
467
|
|
|
args = null; |
|
|
|
|
|
|
468
|
|
|
|
|
469
|
|
|
// One of our video provider domains? |
|
470
|
|
|
if (embedded_count < oSettings.embed_limit && m !== null && typeof handlers[m[1]] !== 'undefined' && handlers[m[1]] !== null) |
|
471
|
|
|
{ |
|
472
|
|
|
// Call the handler and get the tag to insert |
|
473
|
|
|
handler = handlers[m[1]]; |
|
474
|
|
|
|
|
475
|
|
|
// If there are video tags seperated by only a BR node, remove the BR so the video embed can |
|
476
|
|
|
// be side by side on a wide enough screen. |
|
477
|
|
|
if (tag.previousSibling && tag.previousSibling.nodeName === 'BR') |
|
478
|
|
|
{ |
|
479
|
|
|
if (tag.previousSibling.previousElementSibling && tag.previousSibling.previousElementSibling.classList.contains('elk_video_container')) |
|
480
|
|
|
{ |
|
481
|
|
|
tag.previousSibling.remove(); |
|
482
|
|
|
} |
|
483
|
|
|
} |
|
484
|
|
|
|
|
485
|
|
|
args = handler(m[2], tag, provider_class); |
|
486
|
|
|
if (args) |
|
487
|
|
|
{ |
|
488
|
|
|
embedded_count++; |
|
489
|
|
|
$(tag).wrap('<div class="elk_video_container ' + (typeof args[2] !== 'undefined' ? args[2] : '') + '">'); |
|
490
|
|
|
$(tag).wrap('<div class="elk_video_header">').text(args[0]).after(showhideBtn.clone(true)); |
|
491
|
|
|
$(tag).parent().parent().append(args[1]); |
|
492
|
|
|
} |
|
493
|
|
|
} |
|
494
|
|
|
}); |
|
495
|
|
|
|
|
496
|
|
|
// If we have embeded videos, add the lazy load code and events |
|
497
|
|
|
if (embedded_count > 0) |
|
498
|
|
|
{ |
|
499
|
|
|
scrollEmbed(); |
|
500
|
|
|
} |
|
501
|
|
|
|
|
502
|
|
|
/** |
|
503
|
|
|
* Some sites have no thumbnail, so we mimic an onclick to load the embed when the element is on screen. This |
|
504
|
|
|
* provides something other than the default logo. |
|
505
|
|
|
* |
|
506
|
|
|
* Note: This does now work for all sites, like instagram, due to cors errors. For those you need to set the onclick |
|
507
|
|
|
* and let the user load the embed. |
|
508
|
|
|
*/ |
|
509
|
|
|
function scrollEmbed () |
|
510
|
|
|
{ |
|
511
|
|
|
let videoLinks = document.querySelectorAll('a[data-video_embed]'), |
|
512
|
|
|
throttleTimeout, |
|
513
|
|
|
found = false; |
|
514
|
|
|
|
|
515
|
|
|
/** |
|
516
|
|
|
* Function that fires to lazy load video sites embed when in viewport |
|
517
|
|
|
*/ |
|
518
|
|
|
function videoLinksListener () |
|
519
|
|
|
{ |
|
520
|
|
|
if (throttleTimeout) |
|
521
|
|
|
{ |
|
522
|
|
|
clearTimeout(throttleTimeout); |
|
523
|
|
|
} |
|
524
|
|
|
|
|
525
|
|
|
// On scroll fires "a lot" so this tames it to be less abusive |
|
526
|
|
|
throttleTimeout = setTimeout(function() { |
|
527
|
|
|
videoLinks.forEach(function(a) { |
|
528
|
|
|
// No links remaining, drop any listeners |
|
529
|
|
|
if (videoLinks.length === 0) |
|
530
|
|
|
{ |
|
531
|
|
|
document.removeEventListener('scroll', videoLinksListener); |
|
532
|
|
|
window.removeEventListener('resize', videoLinksListener); |
|
533
|
|
|
window.removeEventListener('orientationChange', videoLinksListener); |
|
534
|
|
|
} |
|
535
|
|
|
|
|
536
|
|
|
// Hey I see you ... |
|
537
|
|
|
if (isElementInViewport(a)) |
|
538
|
|
|
{ |
|
539
|
|
|
let func = a.getAttribute('data-video_embed'); |
|
540
|
|
|
found = true; |
|
541
|
|
|
a.removeAttribute('data-video_embed'); |
|
542
|
|
|
imgHandlers[func](a.embedURL, (data) => { |
|
543
|
|
|
createFragment(a, data); |
|
544
|
|
|
}); |
|
545
|
|
|
} |
|
546
|
|
|
}); |
|
547
|
|
|
|
|
548
|
|
|
// Once a link is found, lets not try that one again. |
|
549
|
|
|
if (found) |
|
550
|
|
|
{ |
|
551
|
|
|
videoLinks = document.querySelectorAll('a[data-video_embed]'); |
|
552
|
|
|
} |
|
553
|
|
|
}, 25); |
|
554
|
|
|
} |
|
555
|
|
|
|
|
556
|
|
|
// Scroll, rotate or resize, we check if we can see the video link. |
|
557
|
|
|
document.addEventListener('scroll', videoLinksListener); |
|
558
|
|
|
window.addEventListener('DOMContentLoaded', videoLinksListener); |
|
559
|
|
|
window.addEventListener('resize', videoLinksListener); |
|
560
|
|
|
window.addEventListener('orientationChange', videoLinksListener); |
|
561
|
|
|
} |
|
562
|
|
|
}; |
|
563
|
|
|
})(jQuery); |
|
564
|
|
|
|